package mt.comm;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import mt.Order;
import mt.comm.ClientSideMessage.Type;


/**
 * @author Nuno Pardal
 *
 */
public class CommunicationsClient implements ClientComm, Runnable {

	
	private String host;
	private String nickname;
	
	private Socket socket;
	private int port;
	private ObjectOutputStream outputStream;
	private ObjectInputStream inputStream;
	private int capacity = 50;
	
	private BlockingQueue<ClientSideMessage> queue;
	private Thread thread;
	
	public CommunicationsClient(int port){
		this.port = port;
		queue = new ArrayBlockingQueue<ClientSideMessage>(capacity);
	}
	
	
	
	
	@Override
	public void run() {
		
		try{
			ClientSide message = null;
			boolean disconnect = false;
			
			while(!disconnect){
				message = (ClientSide) 
		        inputStream.readObject();
				queue.put(message);
				if(message.getError() != null || message.getType().equals("DISCONNECT")){
					disconnect = true;
					System.out.println("******");
					this.disconnect();
				}
			}
		} catch (InterruptedException e){
			e.printStackTrace();
		} catch (ClassNotFoundException e){
			e.printStackTrace();
		} catch (EOFException e){
		} catch (IOException e) {
			
		}
	}
	
	/**
	 * Establish a connection to a MicroTrader server. This method should be the first one called by 
	 * the client as no other operation (expect for {@link #isConnected()}) can be performed before
	 * a connection to the server has been established.
	 * 
	 * @param host                    Address of the server. For instance "localhost", "192.168.1.1", and or similar.
	 * @param nickname                The nickname of the user.
	 * @throws UnknownHostException   Thrown if the host cannot be found.
	 * @throws IOException            Thrown if a connection cannot be established.
	 */
	@Override
	public void connect(String host, String nickname)
			throws UnknownHostException, IOException {
		
		this.host = host;
		this.nickname = nickname;
		
		socket = new Socket(this.host, port);
		outputStream = new ObjectOutputStream(socket.getOutputStream());
		inputStream = new ObjectInputStream(socket.getInputStream());
		
		ServerSide message = new ServerSide(ServerSideMessage.Type.CONNECTED, nickname, null);
		outputStream.writeObject(message);
		
		thread = new Thread(this);
		thread.start();
	}
	
	/**
	 * Check if currently connected to a host.
	 * 
	 * @return <b>true</b> if currently connected to a host, <b>false</b> otherwise.
	 */

	@Override
	public boolean isConnected() {
		if (socket != null)
			return true;
		return false;
	}
	
	/**
	 * Disconnects from the server. Any sockets should be closed immediately. 
	 * Pending orders are not guaranteed to be delivered before disconnecting.
	 */

	@Override
	public void disconnect() {
		try{
			ServerSide message = new ServerSide(ServerSideMessage.Type.DISCONNECTED, nickname, null);
			outputStream.writeObject(message);
			inputStream.close();
			outputStream.flush();
			outputStream.close();
			socket.close();
			socket = null;
			
		}catch (IOException e){
			e.printStackTrace();
		}
	}
	
	/**
	 * Get the next message received from the server. If no message 
	 * is has been received, the method blocks until a message has 
	 * been received. If no message will ever be received, null is returned.
	 * 
	 * @return The next message sent by the server to the client or <b>null</b> 
	 *         if no message will ever be received (disconnected from server).
	 */


	@Override
	public ClientSideMessage getNextMessage() {
		ClientSideMessage message = null;
		try {
			message = queue.take();
		}catch (InterruptedException e) {
			e.printStackTrace();
		}
		return message;
	}
	
	/** 
	 * Checks if a message from the server is pending. If {@link #hasNextMessage()} returns true, a call to {@link #getNextMessage} 
	 * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns false, a call to {@link #getNextMessage} 
	 * will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from the server is currently pending, otherwise <b>false</b>.
	 */

	@Override
	public boolean hasNextMessage() {
		return !queue.isEmpty();
	}
		
	/**
	 * Send an order to the server which should then process the order.
	 * 
	 * @param order The MicroTrader order to send.
	 */

	@Override
	public void sendOrder(Order order) {
		ServerSideMessage message = new ServerSide(ServerSideMessage.Type.NEW_ORDER, nickname, order);
		try{
			outputStream.writeObject(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
